/*
 Xern Entity Relocation Network
 Copyright (C) 2011 Ronie Salgado <roniesalg@gmail.com>

 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
 in the Software without restriction, including without limitation the rights
 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 copies of the Software, and to permit persons to whom the Software is
 furnished to do so, subject to the following conditions:

 The above copyright notice and this permission notice shall be included in
 all copies or substantial portions of the Software.

 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 THE SOFTWARE.
*/

#include <boost/uuid/uuid_io.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/date_time/gregorian/gregorian.hpp>
#include <iterator>
#include "Xern/IResourceCache.hpp"
#include "Xern/IRigidBody.hpp"
#include "Xern/ISerializableResource.hpp"
#include "Xern/IServerModule.hpp"
#include "Xern/FileSystem.hpp"
#include "Xern/Log.hpp"
#include "Xern/Region.hpp"
#include "Xern/ResourceManager.hpp"
#include "Xern/WorldManager.hpp"
#include "Xern/Engine.hpp"
#include "ServerSession.hpp"
#include "Message.hpp"

namespace XernProtocol
{
	const posix_time::time_duration ServerSession::ServerInterval = posix_time::milliseconds(10);
	const posix_time::ptime ServerSession::TimeEpoch(boost::gregorian::date(2000, boost::gregorian::Jan, 1), posix_time::seconds(0));

	ServerSession::ServerSession(boost::shared_ptr<Session> session)
		: session(session), transferManager(session)
	{
		lastUpdateTime = TimeEpoch;
		lastPingId = 0;
		lastPingTime = TimeEpoch;
		currentPing = 0;
	}

	ServerSession::~ServerSession()
	{
	}

	void ServerSession::Opened()
	{
		Engine::Get().GetResourceManager()->RegisterResourceListener(shared_from_this());
	}

	void ServerSession::Closed()
	{
		Engine::Get().GetResourceManager()->UnregisterResourceListener(shared_from_this());
	}

	void ServerSession::Think(float delta)
	{
		// Receive the session messages.
		boost::shared_ptr<IMessage> message = session->Receive();
		for(; message; message = session->Receive())
		{
			// Check for a xern message.
			boost::shared_ptr<Message> xmsg =
					boost::dynamic_pointer_cast<Message> (message);
			if(!xmsg)
				continue;

			// Send messages into the transfer manager.
			if(transferManager.ProcessMessage(xmsg))
				continue;

			// Dispatch the message into the lower levels.
			switch(xmsg->GetType())
			{
			case MT_SESSION_PING_REQUEST:
				OnSessionPingRequest(boost::static_pointer_cast<SessionPingRequest> (xmsg));
				break;
			case MT_SESSION_PING_RESPONSE:
				OnSessionPingResponse(boost::static_pointer_cast<SessionPingResponse> (xmsg));
				break;
			case MT_PLAYER_ENTITY_REQUEST:
				OnPlayerRequest(boost::static_pointer_cast<PlayerEntityRequest> (xmsg));
				break;
			case MT_PLAYER_ENTITY_EVENT:
				OnPlayerEvent(boost::static_pointer_cast<PlayerEntityEvent> (xmsg));
				break;
			case MT_REGION_LIST_REQUEST:
				OnRegionListRequest();
				break;
			case MT_REGION_FULL_UPDATE_REQUEST:
				OnRegionFullUpdateRequest(boost::static_pointer_cast<RegionFullUpdateRequest> (xmsg));
				break;
			default:
				LogConsole->Warning("Received unknown message[type %d]", xmsg->GetType());
				break;
			}
		}

		// Don't update if nothing has happened.
		if(CompareFloat(delta, 0.0f) == 0)
			return;

		// Update the client entities.
		UpdateEntities();
	}

	void ServerSession::CheckPing()
	{
	}

	void ServerSession::OnSessionPingRequest(boost::shared_ptr<SessionPingRequest> request)
	{
		// Read the current time.
		posix_time::ptime now = GetCurrentTime();

		// Create the response.
		boost::shared_ptr<SessionPingResponse> resp(new SessionPingResponse());
		resp->pingId = request->pingId;
		resp->receiveTime = (now - TimeEpoch).total_milliseconds();

		// Send it.
		session->Send(resp);
	}

	void ServerSession::OnSessionPingResponse(boost::shared_ptr<SessionPingResponse> response)
	{
	}

	void ServerSession::OnAddedResource(boost::shared_ptr<Entity> entity, boost::shared_ptr<IResource> resource)
	{
		// Only send casheable or serializable resources.
		if((resource->GetFlags() & (IResource::RFL_CASHEABLE | IResource::RFL_SERIALIZABLE)) == 0)
			return;

		// Get the resource manager.
		boost::shared_ptr<ResourceManager> resourceMan = Engine::Get().GetResourceManager();

		// Get the resource cache.
		boost::shared_ptr<IResourceCache> cache = resourceMan->GetResourceCache();

		// Notify the client.
		boost::shared_ptr<EntityAddedResource> message(new EntityAddedResource());
		message->entityId = entity->GetId();
		message->resourceId = resource->GetId();
		message->resourceHash = cache->GetResourceHash(resource);
		message->resourceFlags = resource->GetFlags();

		// Send serializable resources data.
		if(resource->GetFlags() & IResource::RFL_SERIALIZABLE)
		{
			boost::shared_ptr<ISerializableResource> serial = boost::dynamic_pointer_cast<ISerializableResource> (resource);
			if(serial)
			{
				message->resourceMime = serial->GetMimeType();
				message->resourceExtension = serial->GetFileExtension();
			}
		}
		else
		{
			// Ask the cache for the file extension.
			message->resourceExtension = FileExtension(cache->GetResourcePath(resource));
		}

		session->Send(message);
	}

	void ServerSession::OnRemovedResource(boost::shared_ptr<Entity> entity, boost::shared_ptr<IResource> resource)
	{
		// Notify the client.
		boost::shared_ptr<EntityRemovedResource> message(new EntityRemovedResource());
		message->entityId = entity->GetId();
		message->resourceId = resource->GetId();
		session->Send(message);
	}

	void ServerSession::OnUpdateState(boost::shared_ptr<IResource> resource, boost::shared_ptr<IDataFilter> stateData)
	{
		boost::shared_ptr<ResourceState> message(new ResourceState());
		message->resourceId = resource->GetId();

		// Get the state size.
		stateData->Seek(0, IDataFilter::ST_END);
		message->statesize = stateData->GetPosition();

		// Check the state size.
		if(message->statesize > ResourceState::MaxStateSize)
		{
			LogConsole->Error("Resource state update exceed max size.");
			return;
		}

		// Write the state data.
		stateData->Seek(0, IDataFilter::ST_SET);
		stateData->Read(message->statesize, message->state.c_array());

		// Send the message.
		session->Send(message);
	}

	bool ServerSession::OnDelayedLoad(const uuid &id, boost::shared_ptr<IResourceLoadedCallback> callback)
	{
		return false;
	}

	void ServerSession::OnPlayerRequest(boost::shared_ptr<PlayerEntityRequest> request)
	{
		boost::shared_ptr<WorldManager> worldMan = Engine::Get().GetWorldManager();

		// Find the matching region.
		boost::shared_ptr<Region> region;
		const Entity::Children &regions = worldMan->GetChildren();
		Entity::Children::const_iterator it = regions.begin();
		for(; it != regions.end(); it++)
		{
			boost::shared_ptr<Entity> ent = *it;

			// Accept only regions.
			if(ent != ent->GetRegion())
				continue;

			if(ent->GetId() == request->regionId)
			{
				region = boost::static_pointer_cast<Region> (ent);
				break;
			}
		}

		// Only create the player controller if a matching region was found.
		if(region)
		{
			boost::shared_ptr<IServerModule> server = Engine::Get().GetServerModule();
			playerEntity = server->CreatePlayerController(region);
		}

		// Create the response.
		boost::shared_ptr<PlayerEntityResponse> response(new PlayerEntityResponse());

		// Fill the response.
		if(playerEntity)
		{
			response->playerId = playerEntity->GetId();
		}

		// Send it.
		session->Send(response);
	}

	void ServerSession::OnPlayerEvent(boost::shared_ptr<PlayerEntityEvent> event)
	{
		// Ignore if there isn't a player, or it is invalid.
		if(!playerEntity || playerEntity->GetId() != event->playerId)
			return;

		// Create the sender data.
		char sender[64];
		sprintf(sender, "client %d", session->GetSessionID());

		// Send the event into the player.
		playerEntity->OnEvent(event->name, sender, event->data);
	}

	void ServerSession::OnRegionListRequest()
	{
		// Create the response message.
		boost::shared_ptr<RegionListResponse> resp(new RegionListResponse());

		// Fill the response.
		boost::shared_ptr<WorldManager> worldMan = Engine::Get().GetWorldManager();
		const Entity::Children &regions = worldMan->GetChildren();
		Entity::Children::const_iterator it = regions.begin();
		for(; it != regions.end(); it++)
		{
			// Make sure it is actually a region.
			boost::shared_ptr<Entity> ent = *it;
			if(ent->GetRegion() != ent)
				continue;

			// Cast the region.
			boost::shared_ptr<Region> region =
					boost::static_pointer_cast<Region> (ent);

			// Create the region entry.
			RegionListResponse::Entry entry;
			entry.basedata = region->GetFileName();
			entry.name = region->GetName();
			entry.id = region->GetId();
			entry.flags = 0;

			// Store the entry in the message.
			resp->entries.push_back(entry);
		}

		// Send the response.
		session->Send(resp);
	}

	void ServerSession::OnRegionFullUpdateRequest(boost::shared_ptr<RegionFullUpdateRequest> request)
	{
		// Ignore if the player wasn't created.
		if(!playerEntity)
			return;

		// Reset the last update time.
		lastUpdateTime = TimeEpoch;

		// Clear the old region entities.
		oldRegionEntities.clear();

		// Update the entities.
		UpdateEntities();

		// Send the entities resources.
		Region::RegionEntities::const_iterator it = oldRegionEntities.begin();
		for(; it != oldRegionEntities.end(); it++)
		{
			boost::shared_ptr<Entity> entity = *it;
			const Entity::Resources &resources = entity->GetResources();
			Entity::Resources::iterator rit = resources.begin();
			for(; rit != resources.end(); rit++)
			{
				// Trigger added resource event.
				boost::shared_ptr<IResource> resource = *rit;
				OnAddedResource(entity, resource);
			}
		}
	}

	void ServerSession::UpdateEntities()
	{
		if(!playerEntity)
			return;

		// Check if it is time to update.
		posix_time::ptime now = posix_time::microsec_clock::universal_time();
		if(now < lastUpdateTime + ServerInterval)
			return;

		// Get the player region.
		boost::shared_ptr<Region> region = playerEntity->GetRegion();
		if(!region)
			return;

		// Get the region entities.
		Region::RegionEntities entities = region->GetRegionEntities();

		// Update the presence.
		UpdateEntitiesPresence(entities);

		// Update the movement.
		UpdateEntitiesMovement(entities);

		// Update the physics.
		UpdateEntitiesPhysics(entities);

		// Update the last time.
		lastUpdateTime = now;

		// Store the region entities.
		oldRegionEntities.swap(entities);
	}

	void ServerSession::UpdateEntitiesPresence(Region::RegionEntities &newEntities)
	{
		// Calculate the changed elements.
		typedef std::vector<boost::shared_ptr<Entity> > Changed;
		Changed changed(oldRegionEntities.size() + newEntities.size());
		Changed::iterator end = std::set_symmetric_difference(oldRegionEntities.begin(), oldRegionEntities.end(),
									 newEntities.begin(), newEntities.end(), changed.begin());

		// Check that they are actually changed elements.
		if(end == changed.begin())
			return;

		// Build the removed and added messages.
		boost::shared_ptr<EntityAdded> addMessage;
		boost::shared_ptr<EntityRemoved> removeMessage;

		// Process every changed entity.
		Changed::iterator it = changed.begin();
		for(; it != end; it++)
		{
			// Get the affected entity.
			boost::shared_ptr<Entity> ent = *it;

			// Check the change type.
			boost::shared_ptr<Entity> parent = ent->GetParent();
			if(!parent)
			{
				// The entity has been removed.

				// Check if it is necessary to create a new message.
				if(!removeMessage || removeMessage->entities.size() == EntityRemoved::MaxEntitiesPerPacket)
				{
					if(removeMessage)
						session->Send(removeMessage);
					removeMessage.reset(new EntityRemoved());
				}

				// Remove the entity.
				removeMessage->entities.push_back(ent->GetId());
			}
			else
			{
				// The entity has been added.

				// Check if it is necessary to create a new message.
				if(!addMessage || addMessage->entityData.size() == EntityAdded::MaxEntitiesPerPacket)
				{
					if(addMessage)
						session->Send(addMessage);
					addMessage.reset(new EntityAdded());
				}

				// Add the entity.
				EntityAdded::Data data;
				data.parentId = parent->GetId();
				data.id = ent->GetId();
				addMessage->entityData.push_back(data);
			}
		}

		// Send the messages.
		if(addMessage)
			session->Send(addMessage);

		if(removeMessage)
			session->Send(removeMessage);
	}

	void ServerSession::UpdateEntitiesMovement(Region::RegionEntities &entities)
	{
		// Check if there are actually entities.
		size_t numentities = entities.size();
		if(numentities == 0)
			return;

		// Calculate the sent time.
		posix_time::ptime now = GetCurrentTime();
		u64 sentTime = (now - TimeEpoch).total_milliseconds();

		// Update each one of the entities.
		boost::shared_ptr<EntityMovementUpdates> message(new EntityMovementUpdates());
		message->time = sentTime;

		Region::RegionEntities::iterator it = entities.begin();
		for(size_t i = 0; it != entities.end(); i++, it++)
		{
			// Get the entity.
			boost::shared_ptr<Entity> ent = *it;
			boost::shared_ptr<Entity> parent = ent->GetParent();

			// Only update needed entities.
			if(ent->GetLastMovementTime() < lastUpdateTime)
				continue;

			// Create the update.
			EntityMovementUpdates::Update up;

			// Read the transform.
			const ReversibleTransform &trans = ent->GetTransform();

			// Fill the update.
			up.id = ent->GetId();
			up.parentId = parent->GetId();
			up.position = trans.GetVector();
			up.rotation = trans.GetRotation();
			up.velocity = ent->GetLinearVelocity();
			up.acceleration = ent->GetLinearAcceleration();
			up.angularVelocity = ent->GetAngularVelocity();

			// Store the update.
			message->updates.push_back(up);

			// Check if the message limit has been reached.
			if(i+1 < numentities &&
				message->updates.size() == EntityMovementUpdates::MaxUpdatesPerPacket)
			{
				// Send the message and create a new one.
				session->Send(message);
				message.reset(new EntityMovementUpdates());
				message->time = sentTime;
			}
		}

		// Send the message.
		if(!message->updates.empty())
			session->Send(message);
	}

	void ServerSession::UpdateEntitiesPhysics(Region::RegionEntities &entities)
	{
		// Check if there are actually entities.
		size_t numentities = entities.size();
		if(numentities == 0)
			return;

		// Calculate the sent time.
		posix_time::ptime now = GetCurrentTime();
		u64 sentTime = (now - TimeEpoch).total_milliseconds();

		// Update each one of the entities.
		boost::shared_ptr<EntityPhysicUpdates> message(new EntityPhysicUpdates());
		message->time = sentTime;

		Region::RegionEntities::iterator it = entities.begin();
		for(size_t i = 0; it != entities.end(); i++, it++)
		{
			// Get the entity.
			boost::shared_ptr<Entity> ent = *it;
			boost::shared_ptr<IRigidBody> rb = ent->GetRigidBody();

			// Only update needed entities.
			if(ent->GetLastPhysicChangeTime() < lastUpdateTime)
				continue;

			// Create the update.
			EntityPhysicUpdates::Update up;

			// Read the transform.
			const ReversibleTransform &trans = ent->GetTransform();

			// Fill the update.
			up.entity = ent->GetId();
			if(rb)
			{
				up.physicType = (u8)rb->GetType();
				up.mass = rb->GetMass();

				boost::shared_ptr<ICollisionShape> shape = rb->GetCollisionShape();
				if(shape)
					up.collisionShape = shape->GetId();
				else
					up.collisionShape = nil_uuid();
				up.linearFactor = rb->GetLinearFactor();
				up.angularFactor = rb->GetAngularFactor();
				rb->GetSleepingThresholds(up.linearSleepThreshold, up.angularSleepThreshold);
			}
			else
			{
				up.physicType = IRigidBody::RBT_NONE;
				up.mass = 0.0f;
				up.collisionShape = nil_uuid();
			}

			// Store the update.
			message->updates.push_back(up);

			// Check if the message limit has been reached.
			if(i+1 < numentities &&
				message->updates.size() == EntityPhysicUpdates::MaxUpdatesPerPacket)
			{
				// Send the message and create a new one.
				session->Send(message);
				message.reset(new EntityPhysicUpdates());
				message->time = sentTime;
			}
		}

		// Send the message.
		if(!message->updates.empty())
			session->Send(message);
	}

}; // namespace XernProtocol
